package cn.lingyangwl.agile.infra.weixin.mp.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.model.enums.BoolEnum;
import cn.lingyangwl.agile.common.core.exception.enums.GlobalErrorEnum;
import cn.lingyangwl.agile.infra.weixin.mp.assembly.MpUserAssembly;
import cn.lingyangwl.agile.infra.weixin.mp.manager.MpTagManager;
import cn.lingyangwl.agile.infra.weixin.mp.manager.MpUserManager;
import cn.lingyangwl.agile.infra.weixin.mp.mapper.MpUserMapper;
import cn.lingyangwl.agile.infra.weixin.mp.model.WxMpContext;
import cn.lingyangwl.agile.infra.weixin.mp.model.entity.MpTag;
import cn.lingyangwl.agile.infra.weixin.mp.model.entity.MpUser;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpUserPageReq;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpUserResp;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpUserUpdateReq;
import cn.lingyangwl.agile.infra.weixin.mp.handler.WxMpHandler;
import cn.lingyangwl.agile.infra.weixin.mp.service.MpUserService;
import cn.lingyangwl.framework.mybatis.plus.utils.PageUtil;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.result.WxMpUserList;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 公众号粉丝
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class MpUserServiceImpl extends ServiceImpl<MpUserMapper, MpUser>
    implements IService<MpUser>, MpUserService {

    @Resource
    private MpUserManager mpUserManager;
    @Resource
    private MpUserAssembly mpUserAssembly;
    @Resource
    private MpTagManager mpTagManager;
    @Resource
    @Lazy
    private WxMpHandler wxMpHandler;
    
    /**
     * 分页查询 公众号粉丝
     *
     * @return 返回分页数据
     */
    @Override
    public IPage<MpUserResp> pageUser(MpUserPageReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<MpUser> queryWrapper = MpUser.lqw()
            .eq(StringUtils.isNotEmpty(req.getSubscribeStatus()), MpUser::getSubscribeStatus, req.getSubscribeStatus())
            .ge(Objects.nonNull(beginTime), MpUser::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), MpUser::getCreatedAt, endTime)
            .orderByDesc(MpUser::getCreatedAt);

        Map<Long, MpTag> tagMap = mpTagManager.list(MpTag.lqw())
                .stream().collect(Collectors.toMap(MpTag::getTagId, Function.identity()));

        IPage<MpUser> pageData = this.page(PageUtil.to(req), queryWrapper);

        // 1. 判断标签是否已经被删除, 如果已经删除, 则要更新一下数据到数据库中
        List<MpUser> updatedUserList = new ArrayList<>();
        IPage<MpUserResp> rsPage = PageUtil.to(pageData, (e) -> {
            MpUserResp rs = mpUserAssembly.toMpUserRS(e);
            List<Long> tagIds = Optional.ofNullable(e.getTagIds()).orElse(Collections.emptyList());
            String tagNames = tagIds.stream()
                    .map(tagMap::get).filter(Objects::nonNull)
                    .map(MpTag::getName).collect(Collectors.joining(","));

            List<Long> newTagIds = tagIds.stream().map(tagMap::get).filter(Objects::nonNull).map(MpTag::getTagId).collect(Collectors.toList());
            if (newTagIds.size() != tagIds.size()) {
                e.setTagIds(newTagIds);
                updatedUserList.add(e);
            }
            rs.setTagNames(tagNames);
            return rs;
        });

        if (CollectionUtils.isNotEmpty(updatedUserList)) {
            this.updateBatchById(updatedUserList);
        }

        return rsPage;
    }

    /**
     * 根据主键查询 公众号粉丝
     *
     * @param id 主键
     * @return 返回记录，没有返回null
     */
    @Override
    public MpUser getUserById(Long id) {
        return this.getById(id);
    }

    /**
     * 保存 公众号粉丝
     *
     * @param req 新增的记录
     * @return 返回影响行数
     */
    @Override
    public boolean saveUser(String mpAppId, WxMpUser req) {
        if (Objects.isNull(req)) {
            return false;
        }
        MpUser mpUser = mpUserAssembly.toMpUser(mpAppId, req);

        if (Boolean.TRUE.equals(req.getSubscribe())) {
            mpUser.setSubscribeTime(new Date());
        } else {
            mpUser.setUnsubscribeTime(new Date());
        }
        LambdaQueryWrapper<MpUser> lqw = MpUser.lqw().eq(MpUser::getMpAppId, mpAppId).eq(MpUser::getOpenId, req.getOpenId());
        return this.saveOrUpdate(mpUser, lqw);
    }


    /**
     * 更新 公众号粉丝
     *
     * @param req 修改的记录
     * @return 返回影响行数
     */
    @Override
    public boolean updateUser(MpUserUpdateReq req) {
        MpUser mpUser = mpUserAssembly.toMpUser(req);

        // 1. 校验存在
        MpUser user = validateUserExists(req.getId());

        // 2. 更新基本信息到数据库
        boolean ret = this.updateById(mpUser);

        // 3. 更新标签到公众号
        updateUserTag(user.getMpAppId(), user.getOpenId(), req.getTagIds());
    	return ret;
    }

    private MpUser validateUserExists(Long id) {
        MpUser user = mpUserManager.getById(id);
        if (user == null) {
            throw new BizException("用户不存在");
        }
        return user;
    }

    private void updateUserTag(String mpAppId, String openid, List<Long> tagIds) {
        me.chanjar.weixin.mp.api.WxMpService mpService = wxMpHandler.getContext().getService();
        try {
            // 1，先取消原来的标签
            List<Long> oldTagIds = mpService.getUserTagService().userTagList(openid);
            for (Long tagId : oldTagIds) {
                mpService.getUserTagService().batchUntagging(tagId, new String[]{openid});
            }
            // 2，再设置新的标签
            if (CollUtil.isEmpty(tagIds)) {
                return;
            }
            for (Long tagId: tagIds) {
                mpService.getUserTagService().batchTagging(tagId, new String[]{openid});
            }
        } catch (WxErrorException e) {
            throw new BizException("用户标签更新失败-{}", e.getError().getErrorMsg());
        }
    }

    /**
     * 通过id删除 公众号粉丝
     *
     * @param id 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteUserById(Long id) {
    	return this.removeById(id);
    }
    
    /**
     * 通过ids删除 公众号粉丝
     *
     * @param ids 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteUserByIds(List<Long> ids) {
    	return this.removeByIds(ids);
    }

    @Override
    public void updateUserUnsubscribe(String mpAppId, String openId) {
        LambdaUpdateWrapper<MpUser> luw = MpUser.luw()
                .eq(MpUser::getMpAppId, mpAppId)
                .eq(MpUser::getOpenId, openId)
                .set(MpUser::getUnsubscribeTime, new Date())
                .set(MpUser::getSubscribeStatus, BoolEnum.FALSE.getCode());
        if (!this.update(luw)) {
            throw new BizException(GlobalErrorEnum.OPERATION_FAIL_UPDATE);
        }
    }

    @Override
    public void syncUser() {
        // for 循环，避免递归出意外问题，导致死循环
        String nextOpenId = null;
        WxMpContext context = wxMpHandler.getContext();
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            log.info("[syncUser][第({}) 次加载公众号粉丝列表，nextOpenId({})]", i, nextOpenId);
            try {
                nextOpenId = syncUser(context, nextOpenId);
            } catch (WxErrorException e) {
                log.error("[syncUser][第({}) 次同步粉丝异常]", i, e);
                break;
            }
            // 如果 nextOpenid 为空，表示已经同步完毕
            if (StrUtil.isEmpty(nextOpenId)) {
                break;
            }
        }
    }

    private String syncUser(WxMpContext context, String nextOpenid) throws WxErrorException {
        // 1，从公众号流式加载粉丝
        me.chanjar.weixin.mp.api.WxMpService mpService = context.getService();
        WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);
        if (CollUtil.isEmpty(wxUserList.getOpenids())) {
            return null;
        }

        // 2，分批加载粉丝信息
        List<List<String>> openIdsList = CollUtil.split(wxUserList.getOpenids(), 100);
        for (List<String> openIds : openIdsList) {
            log.info("[syncUser][批量加载粉丝信息，openIds [{}]", openIds);
            List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openIds);
            batchSaveUser(wxUsers);
        }

        // 返回下一次的 nextOpenId
        return wxUserList.getNextOpenid();
    }

    private void batchSaveUser(List<WxMpUser> wxUsers) {
        if (CollUtil.isEmpty(wxUsers)) {
            return;
        }
        WxMpContext context = wxMpHandler.getContext();
        // 1. 获得数据库已保存的粉丝列表
        List<MpUser> dbUsers = mpUserManager.listByAppIdAndOpenIds(context.getMpParam().getAppId(),
                wxUsers.stream().map(WxMpUser::getOpenId).collect(Collectors.toList()));
        Map<String, MpUser> openId2Users = dbUsers.stream().collect(Collectors.toMap(MpUser::getOpenId, Function.identity()));

        // 2 根据情况，插入或更新
        List<MpUser> users = mpUserAssembly.toMpUserList(context.getMpParam(), wxUsers);
        List<MpUser> newUsers = new ArrayList<>();
        for (MpUser user : users) {
            MpUser dbUser = openId2Users.get(user.getOpenId());
            if (dbUser == null) { // 新增：稍后批量插入
                newUsers.add(user);
            } else { // 更新：直接执行更新
                user.setId(dbUser.getId());
                mpUserManager.updateById(user);
            }
        }

        // 2.1 批量插入
        if (CollUtil.isNotEmpty(newUsers)) {
            mpUserManager.saveBatch(newUsers);
        }
    }
}
